#include "../config.h"

#include <glibmm/i18n.h>

#if defined(USE_LIBNOTIFY)
# include <libnotify/notify.h>
#elif defined(USE_GROWL)
# include <growl.h>
#else
# warning notification will not work
#endif

#include "log.h"
#include "notify.h"
#include "icon.h"
#include "paths.h"

#include "gtkversion.h"

namespace gpyp {

#ifdef USE_LIBNOTIFY
class LibNotifyNotifier : public UpdatableNotifierBase {
private:
  static bool _cap_icon_svg;

public:
  static bool init() {
    if (notify_init(PACKAGE_NAME)) {
      GList *capabilities = notify_get_server_caps();
      GList *svg = g_list_find_custom(capabilities, "image/svg+xml", (GCompareFunc)strcmp);
      if ((_cap_icon_svg = svg != NULL)) {
        log::log << "LibNotifyNotifier: svg supported" << log::endl;
      }
      g_list_foreach(capabilities, (GFunc)g_free, NULL);
      g_list_free(capabilities);
      return true;
    }
    return false;
  }
  static void uninit() {
    notify_uninit();
  }
  static bool is_initted() {
    return notify_is_initted();
  }

private:
  NotifyNotification *notification_;
  Glib::ustring       summary_;
  Glib::ustring       icon_svg_;
#ifndef DISABLE_STATUS_ICON
  RefStatusIcon       status_icon_;
#endif

protected:
  static void notification_on_closed(NotifyNotification */*notification*/,
#ifdef NOTIFY_CLOSED_HAS_REASON
                                     gint reason,
#endif
                                     gpointer data) {
    ((LibNotifyNotifier *)data)->on_closed_event();
  }

protected:
  inline virtual void on_show() {
    if (get_message().empty()) return;
    GError *err = NULL;
    if (!notify_notification_show(notification_, &err)) {
      log::err << "LibNotifyNotifier: ";
      if (err && err->message) {
        log::err << err->message << log::endl;
      } else {
        log::err << _("failed to show notification") << log::endl;
      }
    }
    if (err) g_error_free(err);
  }
  inline virtual void on_close() {
    GError *err = NULL;
    if (!notify_notification_close(notification_, &err)) {
      log::err << "LibNotifyNotifier: ";
      if (err && err->message) {
        log::err << err->message << log::endl;
      } else {
        log::err << _("failed to close notification") << log::endl;
      }
    }
    if (err) g_error_free(err);
  }
  inline virtual void on_update(const Glib::ustring &message) {
    notify_notification_update(notification_, summary_.c_str(), message.c_str(), icon_svg_.c_str());
  }

  inline virtual void on_set_timeout(int timeout) {
    // #define NOTIFY_EXPIRES_NEVER 0
    if (timeout <= 0) timeout = NOTIFY_EXPIRES_NEVER;
    notify_notification_set_timeout(notification_, timeout);
  }
  inline virtual void on_set_urgency(Urgency urgency) {
    NotifyUrgency urgency_set = NOTIFY_URGENCY_NORMAL;
    switch(urgency) {
    case URGENCY_LOW:      urgency_set = NOTIFY_URGENCY_LOW;      break;
    case URGENCY_NORMAL:   urgency_set = NOTIFY_URGENCY_NORMAL;   break;
    case URGENCY_CRITICAL: urgency_set = NOTIFY_URGENCY_CRITICAL; break;
    }
    notify_notification_set_urgency(notification_, urgency_set);
  }

#ifndef DISABLE_STATUS_ICON
  inline void update_attach_status_icon() {
    if (status_icon_) {
      GtkStatusIcon *si = *status_icon_->enable() ? status_icon_->get()->gobj() : NULL;
      notify_notification_attach_to_status_icon(notification_, si);
    }
  }
#endif

public:
  inline LibNotifyNotifier(const Glib::ustring &summary,
                           const Glib::ustring &icon_name,
                           RefStatusIcon        status_icon) :
    notification_(NULL),
    summary_     (summary)
#ifndef DISABLE_STATUS_ICON
    ,
    status_icon_ (status_icon)
#endif
  {
    if (_cap_icon_svg) {
      icon_svg_ = Glib::build_filename(PKGPIXMAPS_SCALABLE_DIR, icon_name + ".svg");
      if (!Glib::file_test(icon_svg_, Glib::FILE_TEST_IS_REGULAR)) icon_svg_.clear();
    }
    notification_ = notify_notification_new(summary_.c_str(), "", icon_svg_.c_str(), NULL);
#ifndef DISABLE_STATUS_ICON
    update_attach_status_icon();
    if (status_icon_) {
      status_icon_->enable()->signal_changed()
        .connect(sigc::mem_fun(*this, &LibNotifyNotifier::update_attach_status_icon));
    }
#endif
    if (!_cap_icon_svg || !icon_svg_.empty()) {
      Glib::RefPtr<Gdk::Pixbuf> pixbuf = IconManager::load_icon(icon_name, IconManager::ICON_SIZE_32);
      if (pixbuf) notify_notification_set_icon_from_pixbuf(notification_, pixbuf->gobj());
    }
    g_signal_connect(G_OBJECT(notification_), "closed", G_CALLBACK(notification_on_closed), this);
  }
  inline virtual ~LibNotifyNotifier() {
    if (!get_message().empty()) close();
    g_object_unref(notification_);
  }
};

bool LibNotifyNotifier::_cap_icon_svg = false;

class LibNotifyProvider : public NotifyProviderBase {
public:
  inline LibNotifyProvider(Application  &app,
                           RefStatusIcon status_icon) :
    NotifyProviderBase(app, "libnotify",
                       "\n<b>%{Channel.name}</b> "
                       "<span size=\"smaller\" color=\"red\">New!</span>\n%{Channel.longdesc}",
                       "\n<small><b>%{Channel.name}</b>\n%{Channel.longdesc}</small>",
                       F_MARKUP | F_TIMEOUT,
                       new LibNotifyNotifier(_("Notification"), IconManager::GPYP, status_icon),
                       new LibNotifyNotifier(_("Error"), GTK_STOCK_DIALOG_ERROR, status_icon))
  {
  }
};
#endif

#ifdef USE_GROWL
class GrowlNotifier : public NotifierBase {
public:
  static const char *NOTIFICATIONS[];
  typedef NotifyProviderBase::Type Type;

private:
  static GHashTable *_icon_data_hash;

  static GMemoryOutputStream *get_icon_data(const Glib::ustring &icon_name) {
    GMemoryOutputStream *mos = (GMemoryOutputStream *)g_hash_table_lookup(_icon_data_hash, icon_name.c_str());
    if (mos == NULL) {
      Glib::RefPtr<Gdk::Pixbuf> pixbuf = IconManager::load_icon(icon_name, IconManager::ICON_SIZE_48);
      if (pixbuf) {
        GOutputStream *os = g_memory_output_stream_new(NULL, 0, g_realloc, NULL);
        GError *err = NULL;
        if (gdk_pixbuf_save_to_stream(pixbuf->gobj(), os, "png", NULL, &err, NULL)) {
          g_hash_table_insert(_icon_data_hash, g_strdup(icon_name.c_str()), os);
          mos = G_MEMORY_OUTPUT_STREAM(os);
        } else {
          log::err << "GrowlNotifier: failed to create icon data - " << err->message << log::endl;
          g_object_unref(os);
        }
        if (err) g_error_free(err);
      } else {
        log::err << "GrowlNotifier: failed to load icon - " << icon_name << log::endl;
      }
    }
    return mos;
  }
  static bool get_icon_data(const Glib::ustring &icon_name, gpointer &icon_data, size_t &icon_data_size) {
    GMemoryOutputStream *mos = get_icon_data(icon_name);
    if (mos) {
      icon_data      = g_memory_output_stream_get_data(mos);
      icon_data_size = g_memory_output_stream_get_size(mos);
      return true;
    }
    return false;
  }

public:
  static bool init() {
    if (_icon_data_hash == NULL) {
      _icon_data_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_object_unref);
    }
    gpointer icon_data      = NULL;
    gsize    icon_data_size = 0;
    get_icon_data(IconManager::GPYP, icon_data, icon_data_size);
    return growl_init(NOTIFICATIONS, (char *)icon_data, icon_data_size, 0) != 0;
  }
  static void uninit() {
    if (_icon_data_hash) g_hash_table_destroy(_icon_data_hash);
  }
  static bool is_initted() {
    return growl_is_initted() != 0;
  }

private:
  Glib::ustring        name_;
  Glib::ustring        title_;
  GMemoryOutputStream *icon_data_;
  int                  priority_;

protected:
  inline virtual void on_notify(const Glib::ustring &message) {
    gpointer icon_data      = NULL;
    gsize    icon_data_size = 0;
    if (icon_data_) {
      icon_data      = g_memory_output_stream_get_data(icon_data_);
      icon_data_size = g_memory_output_stream_get_size(icon_data_);
    }
    growl_post_notification(name_.c_str(), title_.c_str(), normalize_message(message),
                            priority_, (const char *)icon_data, icon_data_size);
  }
  inline virtual void on_show() {
  }
  inline virtual void on_close() {
  }

  inline virtual void on_set_timeout(int timeout) {
  }
  inline virtual void on_set_urgency(Urgency urgency) {
    switch(urgency) {
    case URGENCY_LOW:      priority_ = -1; break;
    case URGENCY_NORMAL:   priority_ =  0; break;
    case URGENCY_CRITICAL: priority_ =  1; break;
    default:               priority_ =  0; break;
    }
  }

public:
  inline GrowlNotifier(Type                 type,
                       const Glib::ustring &title,
                       const Glib::ustring &icon_name) :
    name_     (NOTIFICATIONS[type]),
    title_    (title),
    icon_data_(get_icon_data(icon_name)),
    priority_ (0)
  {
    if (icon_data_) g_object_ref(icon_data_);
  }
  inline virtual ~GrowlNotifier() {
    if (icon_data_) g_object_unref(icon_data_);
  }
};
const char *GrowlNotifier::NOTIFICATIONS[] = {
  "New channel",
  "Update channel",
  "Error",
  NULL
};
GHashTable *GrowlNotifier::_icon_data_hash = NULL;

class GrowlProvider : public NotifyProviderBase {
public:
  inline GrowlProvider(Application  &app) :
    NotifyProviderBase(app, "growl",
                       "\n%{Channel.name} \n%{Channel.longdesc}",
                       "\n%{Channel.name} \n%{Channel.longdesc}",
                       F_NONE,
                       new GrowlNotifier(TYPE_NEW,    "New channel",    IconManager::GPYP),
                       new GrowlNotifier(TYPE_CHANGE, "Update channel", IconManager::GPYP),
                       new GrowlNotifier(TYPE_ERROR,  "Error",          GTK_STOCK_DIALOG_ERROR))
  {
  }
};
#endif

//========================
// Notify
void Notify::init() {
#ifdef USE_LIBNOTIFY
  if (!LibNotifyNotifier::init()) {
    log::err << "LibNotifyNotifier: failed to initialize" << log::endl;
  }
#endif
#ifdef USE_GROWL
  if (!GrowlNotifier::init()) {
    log::err << "GrowlNotifier: failed to initialize" << log::endl;
  }
#endif
}
void Notify::uninit() {
#ifdef USE_LIBNOTIFY
  LibNotifyNotifier::uninit();
#endif
#ifdef USE_GROWL
  GrowlNotifier::uninit();
#endif
}

Notify::Notify(Application &app, RefStatusIcon status_icon) :
  conf::Element       ("notification"),
  notify_error_       ("notify_error",     true),
  timeout_            ("timeout",            10),
  notify_sound_       ("notify_sound",       ""),
  notify_sound_volume_("notify_sound_volume", 1)
{
  add(notify_error_);
  add(timeout_);
  add(notify_sound_);
  add(notify_sound_volume_);

  {
    RefNotifyProviderBase provider;
#ifdef USE_LIBNOTIFY
    provider.reset(new LibNotifyProvider(app, status_icon));
    providers_.push_back(provider);
    add(provider);
    if (LibNotifyNotifier::is_initted()) provider_ = provider;
#endif
#ifdef USE_GROWL
    provider.reset(new GrowlProvider(app));
    providers_.push_back(provider);
    add(provider);
    if (GrowlNotifier::is_initted()) provider_ = provider;
#endif
  }

  timeout_on_changed();
  timeout_->signal_changed().connect(sigc::mem_fun(*this, &Notify::timeout_on_changed));
}

}/*gpyp*/
